package updown.service.jar;


import cn.hutool.extra.ssh.ChannelType;
import cn.hutool.extra.ssh.JschUtil;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import updown.config.CommonConfig;
import updown.data.SseCacheData;
import updown.domain.ServerHost;
import updown.domain.TaskInfo;
import updown.domain.TaskWrapper;
import updown.util.Constant;
import updown.util.SshUtil;
import updown.util.TaskWrapperContext;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * <p>Title: ww.kaixin.downupservice.serviceInfo </p>
 * <p>Description:updown: - ssh 服务service </p>
 *
 * @Author:jiangkaixin
 * @Date:2021/11/3
 * @Version: 1.0
 */
@Slf4j
@Service
public class SshService {
    @Autowired
    private SseCacheData sseCacheData;

    @Autowired
    private CommonConfig commonConfig;

    @Autowired
    private SshUtilService sshUtilService;

    /**
     * 上传jar包 到指定的服务器 连接 shh 上传 jar
     *
     * @param host
     */
    public TaskInfo uploadToServer(ServerHost host) {
        TaskWrapper taskWrapper = TaskWrapperContext.get();
        TaskInfo taskInfo = uploadToServer(host, taskWrapper);
        return taskInfo;
    }

    /**
     * 上传jar包 到指定的服务器 连接 shh 上传 jar
     *
     * @param serverHost
     * @param file
     */
    public TaskInfo uploadToServer(ServerHost host, TaskWrapper taskWrapper) {
        log.info("执行同步 Jar 到其他 Server 的线程 name:{}，服务和文件地址是:{}", Thread.currentThread().getName(),
                taskWrapper.getServiceInfo().getServiceName() + taskWrapper.getJar().getLocalFileLocation());
        TaskInfo taskInfo = TaskInfo.builder()
                .taskId(taskWrapper.getTaskId())
                .taskDes("上传 jar 包到" + host.toString()).build();
        try {
            if (Constant.fail.equals(sshUtilService.uploadAndOverwriteByServiceName(host, commonConfig.getJarPath(),
                    taskWrapper.getJar().getLocalFileLocation()))) {
                sseCacheData.pushMsg(taskInfo.getTaskDes() + "失败，请手动检查", taskWrapper);
                log.info("uploadToServer 失败，请手动检查 >>>:");
                taskInfo.finishedUnSuccess();
                return taskInfo;
            }
        } catch (Exception e) {
            log.info("uploadToServer error:{}", e);
            sseCacheData.pushMsg(taskInfo.getTaskDes() + "失败，请手动检查", taskWrapper);
            log.info("uploadToServer 失败，请手动检查 >>>:");
            taskInfo.finishedUnSuccess();
            return taskInfo;
        }
        log.info("uploadToServer:{} 服务器，成功", host);
        sseCacheData.pushMsg(taskInfo.getTaskDes() + "成功", taskWrapper);
        taskInfo.finished();
        return taskInfo;
    }


    /**
     * 检查 jar 包信息
     *
     * @param serviceName
     * @return
     */
    public Boolean checkJarInfoForClusterDownUp(TaskWrapper taskWrapper) {
        String serviceName = taskWrapper.getServiceInfo().getServiceName();
        String jarPath = commonConfig.getJarPath() + "/" + serviceName + ".jar";
        boolean jarSame = checkFileSameInServers(taskWrapper, jarPath, true);
        log.info("检查jar包信息(),jarPath:{},是否一致?:{}", jarPath, jarSame);
        return jarSame;
    }

    /**
     * 检查 脚本信息
     *
     * @param serviceName
     * @return
     */
    public Boolean checkScriptInfoForClusterDownUp(TaskWrapper taskWrapper) {
        String serviceName = taskWrapper.getServiceInfo().getServiceName();
        String scriptPath = commonConfig.getScriptPath() + "/" + commonConfig.getStartScriptPrefix() + serviceName + commonConfig.getScriptSuffix();
        log.info("checkScriptInfoForClusterDownUp(),scriptPath:{}", scriptPath);
        boolean scriptSame = checkFileSameInServers(taskWrapper, scriptPath, false);
        log.info("检查重启脚本信息(),scriptPath:{},是否一致?:{}", scriptPath, scriptSame);
        return scriptSame;
    }

    /**
     * 检查 kill 15 脚本信息
     *
     * @param taskWrapper
     * @return
     */
    public Boolean checkScriptKillInfoForClusterDownUp(TaskWrapper taskWrapper) {
        String scriptPath = commonConfig.getScriptPath() + "/" + commonConfig.getKillScript();
        String scriptNinePath = commonConfig.getScriptPath() + "/" + commonConfig.getKillNineScript();
        boolean scriptSame = checkFileSameInServers(taskWrapper, scriptPath, false);
        boolean scriptNineSame = checkFileSameInServers(taskWrapper, scriptNinePath, false);
        log.info("scriptPath:{},scriptNinePath:{},scriptSame:{},scriptNineSame:{}", scriptPath,scriptNinePath,scriptSame,scriptNineSame);
        return scriptSame && scriptNineSame;
    }

    /**
     * 文件在各个 host 中是否一致
     *
     * @param taskWrapper
     * @param filePath
     * @param checkSize   如果是jar 需要检查 size 。如果是 脚本的话，只要检查是否存在，即可。
     * @return
     */
    public boolean checkFileSameInServers(TaskWrapper taskWrapper, String filePath, Boolean checkSize) {
        log.info("checkFileSameInServers(),filePath:{}", filePath);
        List<String> infos = new ArrayList();
        log.info("检查文件，服务器列表是:{}", taskWrapper.getServerHosts());
        for (ServerHost serverHost : taskWrapper.getServerHosts()) {
            Session session = SshUtil.getSession(serverHost);
            ChannelSftp channelSftp = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP);
            SftpATTRS stat = null;
            try {
                stat = channelSftp.stat(filePath);
                String info = null;
                if (checkSize) {
                    info = stat.getSize() + "";
                } else {
                    info = "same";
                }
                infos.add(info);
            } catch (SftpException e) {
                log.info("checkFileSameInServers:{}", e);
                infos.add(UUID.randomUUID().toString());
            }
            JschUtil.close(session);
            JschUtil.close(channelSftp);
        }
        long count = infos.stream().distinct().count();
        if (count > 1) {
            log.info("checkFileSameInServers(),检查文件一致 失败, info 信息不一致，info 列表是:{}", infos);
            return false;
        }
        log.info("checkFileSameInServers(),检查文件一致成功, info 信息一致一致，info 列表是:{}", infos);
        return true;
    }

    /**
     * @param taskWrapper
     * @return
     */
    public long getJarSize(ServerHost serverHost, String jarPath) {
        Session session = SshUtil.getSession(serverHost);
        ChannelSftp channelSftp = (ChannelSftp) JschUtil.openChannel(session, ChannelType.SFTP);
        SftpATTRS stat = null;
        long size = 0;
        try {
            stat = channelSftp.stat(jarPath);
            size = stat.getSize();
            return size;
        } catch (SftpException e) {
            log.info("getJarSize:{}", e);
        }
        return size;
    }

    /**
     * 随机获取一个服务 所在服务其上 jar 包的大小，如果为没有，则返回 0
     *
     * @param taskWrapper
     * @return
     */
    public long getRandomServerForServiceNameJarSize(TaskWrapper taskWrapper) {
        if (taskWrapper.getServerHosts() == null || taskWrapper.getServerHosts().isEmpty()) {
            return 0l;
        }
        ServerHost serverHost = taskWrapper.getServerHosts().get(0);
        String jarPath = commonConfig.getJarPath() + "/" + taskWrapper.getServiceInfo().getServiceName() + ".jar";
        long jarSize = getJarSize(serverHost, jarPath);
        log.info("getRandomServerForServiceNameJarSize: jarSize:{},serverHost:{}", jarSize, serverHost);
        return jarSize;
    }

    /**
     * 检查配置错误
     *
     * @return
     */
    public List<String> checkConfig() {
        List<String> errors = new ArrayList<>();
        if (StringUtils.isEmpty(commonConfig.getStartScriptPrefix())) {
            errors.add("启动脚本前缀未配置");
        }
        if (StringUtils.isEmpty(commonConfig.getKillScript())) {
            errors.add("kill15脚本名称未配置");
        }
        if (StringUtils.isEmpty(commonConfig.getKillNineScript())) {
            errors.add("kill9脚本名称未配置");
        }
        if (StringUtils.isEmpty(commonConfig.getScriptSuffix())) {
            errors.add("启动脚本后缀未配置");
        }
        if (StringUtils.isEmpty(commonConfig.getScriptPath())) {
            errors.add("脚本路径未配置");
        }
        if (StringUtils.isEmpty(commonConfig.getJarPath())) {
            errors.add("Jar包存放位置未配置");
        }
        if (errors.isEmpty()) {
            return null;
        }
        return errors;
    }

    /**
     * jar 包 和 脚本检查
     *
     * @param taskWrapper
     */
    public void doCheckJarAndScript(TaskWrapper taskWrapper) {
        //  1 所有的 jar 包信息需要一致，
        Boolean jarCheckSuccess = checkJarInfoForClusterDownUp(taskWrapper);
        //  2 脚本需要都存在
        Boolean restartScriptCheck = checkScriptInfoForClusterDownUp(taskWrapper);
        Boolean kill15ScriptCheck = checkScriptKillInfoForClusterDownUp(taskWrapper);
        if (!jarCheckSuccess || !restartScriptCheck || !kill15ScriptCheck) {
            log.info("log.info:替换集群检查 脚本和jar 的时候发现不一致，暂停替换操作 >>>:jar:{},restartScript:{},kill15Script:{}", jarCheckSuccess, restartScriptCheck, kill15ScriptCheck);
            sseCacheData.sendLastMsgAndThrowEx("替换集群检查 脚本和jar 的时候发现不一致，暂停替换操作", taskWrapper);
        }
        sseCacheData.pushMsg("检查2：jar 和 重启脚本一致", taskWrapper);
    }

    /**
     * 执行重启脚本
     *
     * @param host
     * @param taskWrapper
     * @return
     */
    public TaskInfo execStart(ServerHost host, TaskWrapper taskWrapper) {
        log.info("execStart()  :serverHost{}", host);
        TaskInfo taskInfo = TaskInfo.builder().taskDes(" 执行 start 重启 脚本 " + host.getHost()).build();
        try {
            // 拼接参数信息  "cd /opt/app" + " && " + "./start_yq-provider.jar"
            String[] commands = new String[3];
            commands[0] = "cd " + commonConfig.getScriptPath();
            commands[1] = "./" + commonConfig.getStartScriptPrefix() + taskWrapper.getServiceInfo().getServiceName() + commonConfig.getScriptSuffix();
            commands[2] = "exit";
            String[] res = sshUtilService.execShell(host, commands);
            log.info("启动脚本:{}", res[0]);
            log.info("错误信息:{}", StringUtils.isEmpty(res[1]) ? "无错误信息" : res[1]);
            if (!StringUtils.isEmpty(res[1])) {
                log.info("执行 重启 脚本出错 error :{}", res[1]);
                sseCacheData.pushMsg(res[1], taskWrapper);
                return taskInfo.finishedUnSuccessAddDes(res[1]);
            }
            return taskInfo.finished();
        } catch (IOException | JSchException e) {
            log.info("执行 重启 脚本出错 IOException | JSchException error :{}", e);
            return taskInfo.finishedUnSuccessAddDes(e.toString());
        }
    }

    /**
     * 执行 kill 15 脚本 返回是否执行成功
     *
     * @param serverHost
     * @param retryTimes
     * @param taskWrapper
     * @return
     */
    public TaskInfo execKill15(ServerHost serverHost, TaskWrapper taskWrapper) {
        log.info("execKill15()  :serverHost{}", serverHost);
        TaskInfo taskInfo = TaskInfo.builder().taskDes(" 执行 kill15 脚本 " + serverHost.getHost()).build();
        try {
            // 拼接参数信息  "cd /opt/app" + " && " + "./killp.sh provider.jar"
            String kill15Command = "cd " + commonConfig.getScriptPath() + " && " +
                    "./" + commonConfig.getKillScript() + " " + taskWrapper.getServiceInfo().getServiceName() + ".jar";
            log.info("kill15 command :{}", kill15Command);
            String[] exec = sshUtilService.exec(serverHost, kill15Command);
            String error = exec[1];
            if (!StringUtils.isEmpty(error)) {
                log.info("执行 kill15 脚本出错 error :{}", error);
                sseCacheData.pushMsg(error, taskWrapper);
                return taskInfo.finishedUnSuccessAddDes(error);
            }
            return taskInfo.finishedAddDes(exec[0]);
        } catch (IOException | JSchException e) {
            log.info("执行 kill15 脚本出错 IOException | JSchException error :{}", e);
            return taskInfo.finishedUnSuccessAddDes(e.toString());
        }
    }

    public TaskInfo kill9(ServerHost host, TaskWrapper taskWrapper) {
        log.info("kill9()  :serverHost{}", host);
        TaskInfo taskInfo = TaskInfo.builder()
                .taskId(taskWrapper.getTaskId())
                .taskDes(" 执行 kill9 脚本 " + host.getHost())
                .build();
        try {
            // 拼接参数信息  "cd /opt/app" + " && " + "./start_yq-provider.jar"
            String[] commands = new String[3];
            commands[0] = "cd " + commonConfig.getScriptPath();
            commands[1] = "./" + commonConfig.getKillNineScript() + " " + taskWrapper.getServiceInfo().getServiceName() + ".jar";
            commands[2] = "exit";
            String[] res = sshUtilService.execShell(host, commands);
            log.info("kill9 启动脚本:{}", res[0]);
            log.info("kill9 错误信息:{}", StringUtils.isEmpty(res[1]) ? "无错误信息" : res[1]);
            if (!StringUtils.isEmpty(res[1])) {
                log.info("执行 kill9 脚本出错 error :{}", res[1]);
                sseCacheData.pushMsg(res[1], taskWrapper);
                return taskInfo.finishedUnSuccessAddDes(res[1]);
            }
            return taskInfo.finished();
        } catch (IOException | JSchException e) {
            log.info("执行 kill9 脚本出错 IOException | JSchException error :{}", e);
            return taskInfo.finishedUnSuccessAddDes(e.toString());
        }
    }
}
